/**
 * @class Ext.Array
 * @singleton
 * @author Jacky Nguyen <jacky@sencha.com>
 * @docauthor Jacky Nguyen <jacky@sencha.com>
 *
 * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
 */
define(function(require) {
	require("../aries");
	var arrayPrototype = Array.prototype, slice = arrayPrototype.slice, supportsSplice = ( function() {
			var array = [], lengthBefore, j = 20;

			if (!array.splice) {
				return false;
			}

			// This detects a bug in IE8 splice method:
			// see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/

			while (j--) {
				array.push("A");
			}

			array.splice(15, 0, "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F");

			lengthBefore = array.length;
			//41
			array.splice(13, 0, "XXX");
			// add one element

			if (lengthBefore + 1 != array.length) {
				return false;
			}
			// end IE8 bug

			return true;
		}()), supportsForEach = 'forEach' in arrayPrototype, supportsMap = 'map' in arrayPrototype, supportsIndexOf = 'indexOf' in arrayPrototype, supportsEvery = 'every' in arrayPrototype, supportsSome = 'some' in arrayPrototype, supportsFilter = 'filter' in arrayPrototype, supportsSort = ( function() {
			var a = [1, 2, 3, 4, 5].sort(function() {
				return 0;
			});
			return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
		}()), supportsSliceOnNodeList = true, ExtArray, erase, replace, splice;

	try {
		// IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
		if ( typeof document !== 'undefined') {
			slice.call(document.getElementsByTagName('body'));
		}
	} catch (e) {
		supportsSliceOnNodeList = false;
	}

	function fixArrayIndex(array, index) {
		return (index < 0) ? Math.max(0, array.length + index) : Math.min(array.length, index);
	}

	/*
	 Does the same work as splice, but with a slightly more convenient signature. The splice
	 method has bugs in IE8, so this is the implementation we use on that platform.

	 The rippling of items in the array can be tricky. Consider two use cases:

	 index=2
	 removeCount=2
	 /=====\
	 +---+---+---+---+---+---+---+---+
	 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
	 +---+---+---+---+---+---+---+---+
	 /  \/  \/  \/  \
	 /   /\  /\  /\   \
	 /   /  \/  \/  \   +--------------------------+
	 /   /   /\  /\   +--------------------------+   \
	 /   /   /  \/  +--------------------------+   \   \
	 /   /   /   /+--------------------------+   \   \   \
	 /   /   /   /                             \   \   \   \
	 v   v   v   v                               v   v   v   v
	 +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
	 | 0 | 1 | 4 | 5 | 6 | 7 |       | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
	 +---+---+---+---+---+---+       +---+---+---+---+---+---+---+---+---+
	 A                               B        \=========/
	 insert=[a,b,c]

	 In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
	 that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
	 must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
	 */
	function replaceSim(array, index, removeCount, insert) {
		var add = insert ? insert.length : 0, length = array.length, pos = fixArrayIndex(array, index), remove, tailOldPos, tailNewPos, tailCount, lengthAfterRemove, i;

		// we try to use Array.push when we can for efficiency...
		if (pos === length) {
			if (add) {
				array.push.apply(array, insert);
			}
		} else {
			remove = Math.min(removeCount, length - pos);
			tailOldPos = pos + remove;
			tailNewPos = tailOldPos + add - remove;
			tailCount = length - tailOldPos;
			lengthAfterRemove = length - remove;

			if (tailNewPos < tailOldPos) {// case A
				for ( i = 0; i < tailCount; ++i) {
					array[tailNewPos + i] = array[tailOldPos + i];
				}
			} else if (tailNewPos > tailOldPos) {// case B
				for ( i = tailCount; i--; ) {
					array[tailNewPos + i] = array[tailOldPos + i];
				}
			}
			// else, add == remove (nothing to do)

			if (add && pos === lengthAfterRemove) {
				array.length = lengthAfterRemove;
				// truncate array
				array.push.apply(array, insert);
			} else {
				array.length = lengthAfterRemove + add;
				// reserves space
				for ( i = 0; i < add; ++i) {
					array[pos + i] = insert[i];
				}
			}
		}

		return array;
	}

	function replaceNative(array, index, removeCount, insert) {
		if (insert && insert.length) {
			if (index < array.length) {
				array.splice.apply(array, [index, removeCount].concat(insert));
			} else {
				array.push.apply(array, insert);
			}
		} else {
			array.splice(index, removeCount);
		}
		return array;
	}

	function eraseSim(array, index, removeCount) {
		return replaceSim(array, index, removeCount);
	}

	function eraseNative(array, index, removeCount) {
		array.splice(index, removeCount);
		return array;
	}

	function spliceSim(array, index, removeCount) {
		var pos = fixArrayIndex(array, index), removed = array.slice(index, fixArrayIndex(array, pos + removeCount));

		if (arguments.length < 4) {
			replaceSim(array, pos, removeCount);
		} else {
			replaceSim(array, pos, removeCount, slice.call(arguments, 3));
		}

		return removed;
	}

	function spliceNative(array) {
		return array.splice.apply(array, slice.call(arguments, 1));
	}

	erase = supportsSplice ? eraseNative : eraseSim;
	replace = supportsSplice ? replaceNative : replaceSim;
	splice = supportsSplice ? spliceNative : spliceSim;

	// NOTE: from here on, use erase, replace or splice (not native methods)...

	aries.augmentObject('aries.Array', {
		toArray : function(obj, start, end) {
			var args = $.makeArray(obj), start = start || 0;
			if (end) {
				return args.slice(start, end)
			}
			return args.slice(start)
		},
		remove : function(array, item) {
			var index = _.indexOf(array, item);
			if (index !== -1) {
				erase(array, index, 1);
			}
			return array;
		},

		/**
		 * Converts a value to an array if it's not already an array; returns:
		 *
		 * - An empty array if given value is `undefined` or `null`
		 * - Itself if given value is already an array
		 * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
		 * - An array with one item which is the given value, otherwise
		 *
		 * @param {Object} value The value to convert to an array if it's not already is an array
		 * @param {Boolean} newReference (Optional) True to clone the given array and return a new reference if necessary,
		 * defaults to false
		 * @return {Array} array
		 */
		from : function(value, newReference) {
			if (value === undefined || value === null) {
				return [];
			}

			if ($.isArray(value)) {
				return (newReference) ? slice.call(value) : value;
			}

			/*var type = typeof value;
			// Both strings and functions will have a length property. In phantomJS, NodeList
			// instances report typeof=='function' but don't have an apply method...
			if (value && value.length !== undefined && type !== 'string' && (type !== 'function' || !value.apply)) {
				return ExtArray.toArray(value);
			}*/

			return [value];
		},
	});
})